package pl.edu.agh.nn.kohonen;

import java.util.HashSet;
import java.util.Set;

import pl.edu.agh.nn.kohonen.distance.Distance;

public class KohonenNeuron {

	private final double[] weights;
	private final KohonenNet network;
	private final Set<KohonenNeuron> neighbours;

	private int winCount;
	private double conscience;

	public KohonenNeuron(KohonenNet network) {
		this.network = network;
		this.weights = new double[network.getKohonenConfiguration().getInputDimension()];
		this.conscience = 1.0;
		for (int i = 0; i < weights.length; ++i) {
			weights[i] = network.getKohonenConfiguration().getMinimumRandomNumber()
					+ Math.random()
					* (network.getKohonenConfiguration().getMaximumRandomNumber() - network.getKohonenConfiguration()
							.getMinimumRandomNumber());
		}
		this.neighbours = new HashSet<KohonenNeuron>();
	}

	public double computeFitness(double[] inputs, Distance distance) {
		return distance.computeDistance(weights, inputs);
	}

	private void modifyWeights(double factor, double[] inputs) {
		for (int i = 0; i < inputs.length; ++i) {
			weights[i] = weights[i] * (1 - factor) + inputs[i] * factor;
		}
	}

	public boolean canCompete() {
		return conscience >= network.getKohonenConfiguration().getConscienceThreshold();
	}

	public int getWinCount() {
		return winCount;
	}

	public double[] getWeights() {
		return weights;
	}

	public void increaseConscience() {
		conscience = Math.min(conscience + 1.0 / network.getNeurons().size(), 1.0);
	}

	public void decreaseConscience() {
		conscience -= network.getKohonenConfiguration().getConscienceThreshold();
	}

	public void denoteWin() {
		++winCount;
	}

	public Set<KohonenNeuron> getNeighbours() {
		return neighbours;
	}

	public void pullNetworkTowards(double[] inputs) {
		modifyWeights(network.getCurrentLearningRate(), inputs);

		Set<KohonenNeuron> all = new HashSet<KohonenNeuron>();
		all.add(this);
		Set<KohonenNeuron> edge = new HashSet<KohonenNeuron>();
		edge.add(this);

		int distance = 0;
		while (true) {
			++distance;
			Set<KohonenNeuron> layer = new HashSet<KohonenNeuron>();
			for (KohonenNeuron e : edge) {
				e.addImmediateNeighbours(layer, all);
			}
			if (layer.isEmpty()) {
				break;
			}
			double factor = network.getCurrentLearningRate();
			if (network.getKohonenConfiguration().getUseGaussianNeighbourhood()) {
				factor *= Math.exp(-Math.pow(distance, 2) / (2 * Math.pow(network.getCurrentNeighbourhood(), 2)));
			} else {
				factor *= distance > network.getCurrentNeighbourhood() ? 0.0 : 1.0;
			}
			if (factor < 0.0001) {
				break;
			}
			for (KohonenNeuron l : layer) {
				l.modifyWeights(factor, inputs);
			}
			all.addAll(layer);
			edge = layer;
		}
	}

	private void addImmediateNeighbours(Set<KohonenNeuron> layer, Set<KohonenNeuron> all) {
		for (KohonenNeuron neighbour : neighbours) {
			if (!all.contains(neighbour)) {
				layer.add(neighbour);
			}
		}
	}

	public double getConscience() {
		return conscience;
	}

}
